﻿//----------------------------------------------------------------------------------------------------------------------
// <copyright file="PomodoroRepository.cs" company="Keith B. Rimington">
//
//   Copyright (c) 2011 Keith B. Rimington
//
//   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
//     documentation files (the "Software"), to deal in the Software without restriction, including without limitation
//     the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
//     to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
//   The above copyright notice and this permission notice shall be included in all copies or substantial portions of
//     the Software.
//
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//     THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
//     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//     DEALINGS IN THE SOFTWARE.
//
// </copyright>
//----------------------------------------------------------------------------------------------------------------------

namespace Noc.Pomodoro.Core
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Noc.Pomodoro.ViewModels;
    using Lookup = Noc.Pomodoro.ViewModels.Lookup;

    /// <summary>
    /// Simple data access provider for the application.
    /// </summary>
    public class PomodoroRepository
    {
        public TaskViewModel AddTask(TaskViewModel task)
        {
            using (var model = new Models.PomodoroDb())
            {
                if (task == null)
                {
                    task = new TaskViewModel();
                }

                Models.Task entity = AddTaskInternal(model);
                entity.CopyTo(task);
                return task;
            }
        }

        public IEnumerable<TaskViewModel> GetTasksByDate(DateTime date)
        {
            date = date.Date;
            var endofdate = date.AddDays(1);

            using (var model = new Models.PomodoroDb())
            {
                TaskViewModel taskvm;
                PomodoroViewModel pomovm;
                var qtasks = from task in model.Tasks.Include("Pomodoros").Include("Pomodoros.Events")
                             where (task.Modified >= date && task.Modified < endofdate)
                                || (task.Closed == null && task.Created < endofdate)
                                || (task.Closed != null && task.Closed >= date && task.Closed < endofdate)
                                || task.Pomodoros.Any(pomo => pomo.Started >= date && pomo.Started <= endofdate)
                             orderby task.StateId, task.Title
                             select task;

                foreach (var task in qtasks)
                {
                    taskvm = new TaskViewModel()
                    {
                        Id = task.Id,
                        Title = task.Title,
                        Closed = task.Closed,
                        TaskType = (Lookup.TaskState)task.StateId,
                    };

                    var qpomos = from pomo in task.Pomodoros
                                 where pomo.StateId == (int)Lookup.PomodoroState.NotStarted
                                    || (pomo.Started >= date && pomo.Started <= endofdate)
                                 orderby
                                     pomo.Started ?? DateTime.Today.AddYears(100),
                                     pomo.Modified
                                 select pomo;

                    foreach (var pomo in qpomos)
                    {
                        taskvm.Pomodoros.Add(pomovm = new PomodoroViewModel()
                        {
                            Id = pomo.Id,
                            IsPlanned = pomo.IsPlanned,
                            State = (Lookup.PomodoroState)pomo.StateId
                        });

                        var qevts = pomo.Events.Where(evt => evt.TypeId == (int)Lookup.PomodoroEventType.InternalInterrupt || evt.TypeId == (int)Lookup.PomodoroEventType.ExternalInterrupt)
                                               .OrderBy(evt => evt.Date);

                        foreach (var evt in qevts)
                        {
                            pomovm.Events.Add(new EventViewModel() { Date = evt.Date, EventType = (Lookup.PomodoroEventType)evt.TypeId });
                        }
                    }

                    yield return taskvm;
                }
            }
        }

        public TaskViewModel UpdateTask(TaskViewModel task)
        {
            using (var model = new Models.PomodoroDb())
            {
                var entity = model.Tasks.Include("Pomodoros").Single(t => t.Id == task.Id);
                entity.Modified = DateTime.Now;
                entity.Title = task.Title;
                entity.StateId = (int)task.TaskType;
                entity.Closed = task.Closed;

                // Find deleted pomodoros and remove them from the model.
                // The client is expected to remove the pomodoro from the view model.
                foreach (var pomodoro in task.Pomodoros.Where(p => p.IsDeleted).ToArray())
                {
                    var pomoentity = entity.Pomodoros.SingleOrDefault(p => p.Id == pomodoro.Id);
                    model.DeleteObject(pomoentity);
                    task.Pomodoros.Remove(pomodoro);
                }

                // Find inserted pomodoros and add them to the model.
                foreach (var pomodoro in task.Pomodoros.Where(p => p.Id == Guid.Empty))
                {
                    var pomoentity = AddPomodoroInternal(model, task.Id, pomodoro);
                    pomoentity.CopyTo(pomodoro);
                }

                // Match up model and view model pomodoros, and make changes to the state where appropriate
                var joinedQuery = from pomodoro in task.Pomodoros
                                  join pomoentity in entity.Pomodoros on pomodoro.Id equals pomoentity.Id
                                  select new { ViewModel = pomodoro, Entity = pomoentity };
                foreach (var pair in joinedQuery)
                {
                    if (pair.Entity.StateId != (int)pair.ViewModel.State)
                    {
                        if (pair.ViewModel.State == Lookup.PomodoroState.Running)
                        {
                            pair.Entity.Started = DateTime.Now;
                        }
                        else if (pair.ViewModel.State == Lookup.PomodoroState.Voided || pair.ViewModel.State == Lookup.PomodoroState.Completed)
                        {
                            pair.Entity.Stopped = DateTime.Now;
                        }

                        foreach (var evt in pair.ViewModel.Events.Where(e => e.Id == Guid.Empty))
                        {
                            evt.Id = Guid.NewGuid();
                            pair.Entity.Events.Add(new Models.PomoEvent() { Id = evt.Id, Date = evt.Date, TypeId = (int)evt.EventType });
                        }

                        pair.Entity.StateId = (int)pair.ViewModel.State;
                        pair.Entity.Modified = DateTime.Now;
                    }
                }

                model.SaveChanges();
            }

            return task;
        }

        private static Models.Task AddTaskInternal(Models.PomodoroDb model)
        {
            Models.Task entity;
            model.Tasks.AddObject(entity = new Models.Task()
            {
                Id = Guid.NewGuid(),
                Title = string.Empty,
                StateId = (int)Lookup.TaskState.Activity,
                Created = DateTime.Now,
                Modified = DateTime.Now,
            });
            model.SaveChanges();
            return entity;
        }

        private static Models.Pomodoro AddPomodoroInternal(Models.PomodoroDb model, Guid taskId, PomodoroViewModel pomodoro)
        {
            Models.Pomodoro entity;
            var taskEntity = model.Tasks.Single(t => t.Id == taskId);
            taskEntity.Modified = DateTime.Now;
            taskEntity.Pomodoros.Add(entity = new Models.Pomodoro()
            {
                Id = Guid.NewGuid(),
                IsPlanned = pomodoro.IsPlanned,
                StateId = (int)pomodoro.State,
                Created = DateTime.Now,
                Modified = DateTime.Now,
                Started = pomodoro.State == Lookup.PomodoroState.NotStarted ? (DateTime?)null : DateTime.Now,
                Stopped = pomodoro.State == Lookup.PomodoroState.NotStarted || pomodoro.State == Lookup.PomodoroState.Running ? (DateTime?)null : DateTime.Now,
            });

            return entity;
        }

        private static void RemovePomodoroInternal(PomodoroViewModel pomodoro, Models.PomodoroDb model)
        {
            var entity = model.Pomodoros.SingleOrDefault(p => p.Id == pomodoro.Id);
            if (entity != null)
            {
                model.Pomodoros.DeleteObject(entity);
            }
        }
    }
}